NOIP2022 T1 种花 题解

Stickman Cheng Lv1

题面解释

给定大小的花园,有一些不能种花的土坑,现给定所有格的状态,求在花园里种出"C"形和"F"形的方案数。
"C"形:

1
2
3
4
**
*
**
//至少宽两格高三格

"F"形:

1
2
3
4
5
**
*
**
*
//至少宽两格高四格

解法

要保证不重不漏,我们枚举每个点为左上角,让该点所在行为"C"形上方的横,它的长度有若干种情况,对于这个点,我们设,表示这个点到其右方最近土坑的距离,则该行的方案数为。递推式:

为土坑,

可种花,

接着我们考虑"C"形下面一行,方案数为,枚举从行开始每一行,累加方案数,与第行方案数相乘得答案,设

无法向下延伸时break;

匚,受物之器。 同队大佬亅匚丫

观察到“F”形一竖长度也有若干种情况,考虑“C”形左上角,只需乘以“C”形下方横行方案数按照求的方法累加相乘即可。

表示这个点到其下方最近土坑的距离,则以作为“C”形左上角向下延伸为“F”形的方案数为。递推式:

为土坑,

可种花,

所有点的方案数相加即答案。复杂度,考场上能过!

显然的优化:求答案时加的一维枚举可以省去。

观察求的式子,它们都包含了从当前行的下方第二行到末尾行的方案数。考虑从下方行的答案递推上方行的答案。注意当前行的状态并不一定来自其下方第一行:可能有土坑,也可能它右边第一格为土坑导致无法延伸,该点答案为

因此维护一个表示能转移到的行数,表示无法转移,此时用上述方法暴力求算。

行到行枚举行数:初始,成功转移后表示它可用于转移上方状态。上方行被枚举到时如果为土坑将重置为0,否则

改写一下前面的递推式:

注意除改为乘逆元(打表打个10KB

但这个转移式是错误的。当从下方第二行及更远的地方转移时,这行的方案数对于第行是合法的,因此应加入。

在求时我们忽略这一行,但这一行对于上面所有行都是合法的。在累加进答案后,我们将这一行加进来才可以正确转移。

正确的递推式:

最后,

复杂度。贴上优雅的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <bits/stdc++.h>
using namespace std;
namespace Kochi
{
template<typename Kopi>
inline void read(Kopi& ret)
{
ret=0;
bool f=0;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<3)+(ret<<1)+(ch^48);
ch=getchar();
}
if(f) ret=-ret;
return;
}
const int maxn=1003,mod=998244353;
int rt[maxn][maxn];
int dw[maxn][maxn];
int nxt[maxn][maxn];
long long inv[maxn];
char gd[maxn][maxn];
long long ansc[maxn][maxn];
long long ansf[maxn][maxn];
long long ascc,asff;
int T,id;
int n,m,c,f;
int Ayatsuki()
{
read(T),read(id);
inv[1]=1;
for(int i=2;i<=1000;i++)
inv[i]=(mod-inv[mod%i]*(mod/i)%mod);
while(T--)
{
read(n),read(m),read(c),read(f);
if(c==0&&f==0)
{
printf("0 0\n");
continue;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
gd[i][j]=getchar();
while(gd[i][j]==' '||gd[i][j]=='\n')
gd[i][j]=getchar();
}
for(int i=n;i>=1;i--)
for(int j=m;j>=1;j--)
{
if(i==n)
dw[i][j]=(gd[i][j]=='0')?1:0;
else
dw[i][j]=(gd[i][j]=='1')?0:(dw[i+1][j]+1);
if(j==m)
rt[i][j]=(gd[i][j]=='0')?1:0;
else
rt[i][j]=(gd[i][j]=='1')?0:(rt[i][j+1]+1);
}
ascc=asff=0;
for(int i=n-2;i>=1;i--)
for(int j=1;j<=m-1;j++)
{
ansc[i][j]=ansf[i][j]=0;
if(i==n-2) nxt[i][j]=0;
else nxt[i][j]=nxt[i+1][j];
if(rt[i][j]<2||dw[i][j]<3)
{
if(dw[i][j]==0) nxt[i][j]=0;
continue;
}
if(!nxt[i][j])
{
for(int k=i+2;k<=n;k++)
{
if(gd[k][j]=='1') break;
ansc[i][j]=(ansc[i][j]+(rt[i][j]-1)*(rt[k][j]-1)%mod)%mod;
ansf[i][j]=(ansf[i][j]+(rt[i][j]-1)*(rt[k][j]-1)%mod*(dw[k][j]-1)%mod)%mod;
}
nxt[i][j]=i;
}
else
{
int k=nxt[i][j];
ansc[i][j]=(ansc[k][j]*inv[rt[k][j]-1]%mod+(k-i>1)*(rt[k][j]-1)%mod)*(rt[i][j]-1)%mod;
ansf[i][j]=(ansf[k][j]*inv[rt[k][j]-1]%mod+(k-i>1)*(rt[k][j]-1)*(dw[k][j]-1)%mod)*(rt[i][j]-1)%mod;
nxt[i][j]=i;
}
ascc=(ascc+ansc[i][j]%mod)%mod;
asff=(asff+ansf[i][j]%mod)%mod;
ansc[i][j]=(ansc[i][j]+(rt[i][j]-1)*(rt[i+1][j]-1)%mod)%mod;
ansf[i][j]=(ansf[i][j]+(rt[i][j]-1)*(rt[i+1][j]-1)%mod*(dw[i+1][j]-1)%mod)%mod;
}
printf("%lld %lld\n",c*ascc,f*asff);
}
return 0;
}
}
int main()
{
// freopen("plant.in","r",stdin);
// freopen("plant.out","w",stdout);
return Kochi::Ayatsuki();
}
//it's the only one chance
//now it's your turn,stickman!

如果要更简洁的解法,可以试试枚举左下角,复杂度相同但可能更快。这个交给你们自己实现。

Stickman的小窝 省一爷爷coding_hong

  • 标题: NOIP2022 T1 种花 题解
  • 作者: Stickman Cheng
  • 创建于 : 2022-12-10 19:44:17
  • 更新于 : 2025-04-18 14:50:56
  • 链接: https://stickman.top/2022/12/10/NOIP2022 T1 种花 题解/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
NOIP2022 T1 种花 题解